# +----------------------------------------------------------------------
# | ChatWork智能聊天办公系统
# +----------------------------------------------------------------------
# | 软件声明: 本系统并非自由软件,未经授权任何形式的商业使用均属非法。
# | 版权保护: 任何企业和个人不允许对程序代码以任何形式任何目的复制/分发。
# | 授权要求: 如有商业使用需求,请务必先与版权所有者取得联系并获得正式授权。
# +----------------------------------------------------------------------
# | Author: ChatWork Team <2474369941@qq.com>
# +----------------------------------------------------------------------
import hashlib
import os
import re
import shutil
import zipfile
from tortoise import Tortoise
from tortoise.transactions import in_transaction
from config import get_settings
from exception import AppException
from common.utils.tools import ToolsUtil
from hypertext import PagingResult
from common.utils.urls import UrlUtil
from apps.admin.schemas.system import upgrade_schema as schema


class UpgradeService:
    """ PS: 当前功能尝未完成 """
    BASE_URL = "https://server.mddai.cn"
    PRODUCT_CODE = "c46ddcee63781baf994ff2ac4fcd1c9d"

    @classmethod
    async def lists(cls, params: schema.UpgradeSearchIn):
        """ 版本列表 """
        url = (
            f"{cls.BASE_URL}/indexapi/version/lists?type=2"
            f"&page=1&page_no={params.page_no}&page_size={params.page_size}"
            f"&action=lists&product_code={cls.PRODUCT_CODE}"
        )

        # 当前系统版本
        version: str = get_settings().VERSION
        local_version: int = int(version.replace('.', ''))

        # 远程版本列表
        lists = []
        results = await ToolsUtil.get_curl(url)
        for item in results["data"]["lists"]:
            improve = [t["content"] for t in item["update_content"] if t["type"] == 1]
            repairs = [t["content"] for t in item["update_content"] if t["type"] == 2]
            optimize = [t["content"] for t in item["update_content"] if t["type"] == 3]

            able_update = False  # 允许更新
            curr_version = False  # 当前版本
            version_no = int(item['version_no'].replace('.', ''))
            if version_no == local_version:
                curr_version = True
            if version_no > local_version:
                able_update = True

            lists.append(schema.UpgradeListVo(
                id=item["id"],
                publish_time=item["publish_time"],
                version_no=item["version_no"],
                remarks=item["publish_content"],
                improve=improve,
                repairs=repairs,
                optimize=optimize,
                able_update=able_update,
                curr_version=curr_version
            ))

        return PagingResult.create(
            data=lists,
            total=int(results["data"]["count"]),
            page_no=params.page_no,
            page_size=params.page_size
        )

    @classmethod
    async def upgrade(cls, post: schema.UpgradeSystemIn):
        """ 在线更新 """
        zip_upgrade_dir = UrlUtil.to_runtime_path("/upgrade")
        un_zip_temp_dir = UrlUtil.to_runtime_path("/upgrade/unzip")
        zip_file_path = f"{zip_upgrade_dir}/{post.version_no}.zip"

        try:
            # 取授权链接
            results_ver: dict = await cls.verify(post.version_id)
            package_url: str = results_ver.get("link")

            # 下载补丁包
            await ToolsUtil.download_file(url=package_url, save_path=zip_file_path)

            # 解压补丁包
            with zipfile.ZipFile(zip_file_path) as z:
                z.extractall(un_zip_temp_dir)

            async with in_transaction("mysql"):
                # 更新MYSQL
                await cls.upgrade_mysql(path=f"{un_zip_temp_dir}/project/sql/mysql.sql")

                # 更新PGSQL
                await cls.upgrade_mysql(path=f"{un_zip_temp_dir}/project/sql/pgsql.sql")

                # 更新文件
                root_path: str = UrlUtil.to_root_path()
                await cls.upgrade_file(package_path=f"{un_zip_temp_dir}/project/server", project_path=root_path)
        except Exception as e:
            raise AppException(f"系统更新失败: {str(e)}")

    @classmethod
    async def upgrade_mysql(cls, path: str):
        """ 更新MYSQL """
        # SQL文件不存在则无需处理
        if not os.path.exists(path):
            return True

        # 读取SQL
        content: str = await ToolsUtil.read_file(path=path)

        # 执行SQL
        sql_prefix = os.getenv("MYSQL_PREFIX", "").strip()
        sql_lists = content.split(";")
        for sql in sql_lists:
            sql = sql.strip()
            if sql:
                sql = sql.replace("`cw_", f"`{sql_prefix}") + ";"
                await Tortoise.get_connection("mysql").execute_query(sql)

    @classmethod
    async def upgrade_pgsql(cls, path: str):
        """ 更新PGSQL """
        # SQL文件不存在则无需处理
        if not os.path.exists(path):
            return True

        # 读取SQL
        content: str = await ToolsUtil.read_file(path=path)

        # 执行SQL
        sql_prefix = os.getenv("PGSQL_PREFIX", "").strip()
        sql_lists = content.split(";")
        for sql in sql_lists:
            sql = sql.strip()
            if sql:
                sql = sql.replace("cw_", f"`{sql_prefix}") + ";"
                await Tortoise.get_connection("pgsql").execute_query(sql)

    @classmethod
    async def upgrade_file(cls, package_path: str, project_path: str):
        """ 更新文件 """
        # 验证路径是否为空
        if not package_path.strip() or not project_path.strip():
            return

        # 目录不存在就新建
        if not os.path.exists(project_path):
            os.makedirs(project_path, exist_ok=True)

        for filename in os.listdir(package_path):
            # 要处理的是目录时: 递归处理文件目录
            full_file_name = os.path.join(package_path, filename)
            if os.path.isdir(full_file_name):
                await cls.upgrade_file(
                    full_file_name + os.sep,
                    os.path.join(project_path, filename) + os.sep
                )

            # 要处理的是文件时: 判断是否存在或与原来文件不一致则覆盖
            if os.path.isfile(full_file_name):
                target_file_name = os.path.join(project_path, filename)
                if not os.path.exists(target_file_name):
                    shutil.copy2(full_file_name, target_file_name)
                else:
                    source = hashlib.md5(open(full_file_name, "rb").read()).hexdigest()
                    target = hashlib.md5(open(target_file_name, "rb").read()).hexdigest()
                    if source != target:
                        shutil.copy2(full_file_name, target_file_name)

    @classmethod
    async def upgrade_version(cls, new_version: str, is_replaced: bool = False):
        """ 更新版本号 """
        # 配置文件路径
        file_path = UrlUtil.to_root_path() + ".env"

        # 读取文件内容
        file_contents = await ToolsUtil.read_file(file_path)

        # 验证写入权限
        if not os.access(file_path, os.W_OK):
            raise Exception("server/.env 不具有写入权限")

        # 替换最新版本
        if is_replaced:
            # 用正则表达式替换版本号
            version_pattern = re.compile(r'(VERSION_NO=)\d+\.\d+\.\d+')
            updated_contents = version_pattern.sub(rf"\1{new_version}", file_contents)

            # 将更新后的内容写回文件
            await ToolsUtil.write_file(file_path, updated_contents)

    @classmethod
    async def verify(cls, version_id: int):
        domain = "www.php-cw.localhost"
        remote_url = (
            f"{cls.BASE_URL}/indexapi/version/verify?type=2"
            f"&domain={domain}&version_id={version_id}&link=package_link"
            f"&verify=lists&product_code={cls.PRODUCT_CODE}"
        )

        """ 授权验证 """
        a = await ToolsUtil.get_curl(url=remote_url)
        url = "https://yixiangonline.oss-cn-shenzhen.aliyuncs.com/upgrade/mdd_chat_work/4.2.6/4.2.6-1511-server.zip"
        return {
            "msg": "",
            "link": url,
            "has_permission": False
        }
